feat(customcurrencies): support usage-based credit then invoice - #4780
Conversation
📝 WalkthroughWalkthroughThis PR adds custom-currency overage conversion to fiat, validated invoice lines, custom overage accrual callbacks, updated realization and invoice flows, zero-fiat payment handling, collection-time support, and credit-then-invoice lifecycle tests. ChangesCustom-currency overage flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant UsageBasedService
participant RealizationRun
participant LineMapper
participant InvoiceService
participant PaymentService
UsageBasedService->>RealizationRun: create and allocate realization
RealizationRun->>LineMapper: custom-currency overage totals
LineMapper->>InvoiceService: fiat overage invoice line
InvoiceService->>PaymentService: positive fiat amount
PaymentService-->>InvoiceService: authorization and settlement
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
openmeter/billing/charges/usagebased/service/run/create.go (1)
180-219: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winExtract the shared "custom-currency overage → fiat-zero" rule instead of duplicating it.
Both sites independently reimplement "round totals, and for custom currency, convert overage to fiat to decide if
NoFiatTransactionRequired/no-fiat-transaction should be true" — with a subtle difference (one skips the conversion call when totals are already zero, the other always runs it). Same effective result today, but two hand-rolled copies of this rule are an easy way for future changes to silently diverge.
openmeter/billing/charges/usagebased/service/run/create.go#L180-L219: extract this block'snoFiatTransactionRequiredderivation (lines 196-207) into a small shared helper (e.g. a method onCharge) that takes the rounded totals and settlement-mode flag.openmeter/billing/charges/usagebased/service/creditheninvoice.go#L877-L892: replace this block'snoFiatTransactionRequiredderivation with a call to the same shared helper, dropping the divergent unguarded implementation.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@openmeter/billing/charges/usagebased/service/run/create.go` around lines 180 - 219, Extract the custom-currency overage-to-fiat-zero decision from the noFiatTransactionRequired derivation in openmeter/billing/charges/usagebased/service/run/create.go#L180-L219 into a shared Charge helper accepting rounded totals and the settlement-mode flag, preserving the existing zero-total guard and conversion behavior. Replace the duplicated derivation in openmeter/billing/charges/usagebased/service/creditheninvoice.go#L877-L892 with the same helper so both paths use one rule.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@openmeter/billing/charges/usagebased/service/linemapper.go`:
- Around line 207-248: Preserve the existing FeatureKey when replacing
stdLine.UsageBased in populateCustomCurrencyOverageFromRun. Capture the current
stdLine.UsageBased.FeatureKey before constructing the new
billing.UsageBasedLine, then assign it to the replacement line while leaving the
other overage fields unchanged.
In `@openmeter/ledger/chargeadapter/usagebased.go`:
- Around line 179-186: The OnCustomCurrencyOverageAccrued method lacks the
inline implementation TODO requested for deferred custom-currency overage
booking. Add a TODO[implement] comment at the unsupported-return stub in
OnCustomCurrencyOverageAccrued, while preserving its validation and
meta.ErrCustomCurrencyNotSupported behavior.
---
Nitpick comments:
In `@openmeter/billing/charges/usagebased/service/run/create.go`:
- Around line 180-219: Extract the custom-currency overage-to-fiat-zero decision
from the noFiatTransactionRequired derivation in
openmeter/billing/charges/usagebased/service/run/create.go#L180-L219 into a
shared Charge helper accepting rounded totals and the settlement-mode flag,
preserving the existing zero-total guard and conversion behavior. Replace the
duplicated derivation in
openmeter/billing/charges/usagebased/service/creditheninvoice.go#L877-L892 with
the same helper so both paths use one rule.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 260fe640-8a04-447c-9481-989a565f8406
📒 Files selected for processing (29)
.agents/skills/charges/SKILL.mdopenmeter/billing/charges/meta/customcurrency.goopenmeter/billing/charges/meta/customcurrency_test.goopenmeter/billing/charges/models/creditpurchase/detailedline.goopenmeter/billing/charges/models/creditpurchase/detailedline_test.goopenmeter/billing/charges/service/base_test.goopenmeter/billing/charges/service/flatfee_costbasis_test.goopenmeter/billing/charges/service/handlers_test.goopenmeter/billing/charges/service/usagebased_costbasis_test.goopenmeter/billing/charges/service/usagebased_test.goopenmeter/billing/charges/testutils/handlers.goopenmeter/billing/charges/usagebased/charge.goopenmeter/billing/charges/usagebased/handler.goopenmeter/billing/charges/usagebased/service/create.goopenmeter/billing/charges/usagebased/service/creditheninvoice.goopenmeter/billing/charges/usagebased/service/creditsonly.goopenmeter/billing/charges/usagebased/service/lineengine.goopenmeter/billing/charges/usagebased/service/linemapper.goopenmeter/billing/charges/usagebased/service/linemapper_test.goopenmeter/billing/charges/usagebased/service/run/create.goopenmeter/billing/charges/usagebased/service/run/invoice.goopenmeter/billing/charges/usagebased/service/run/payment.goopenmeter/billing/charges/usagebased/service/run/payment_test.goopenmeter/billing/charges/usagebased/service/run/service.goopenmeter/billing/consts.goopenmeter/billing/service/invoicecalc/collectionat.goopenmeter/billing/service/invoicecalc/collectionat_test.goopenmeter/ledger/chargeadapter/usagebased.goopenmeter/ledger/chargeadapter/usagebased_test.go
💤 Files with no reviewable changes (2)
- openmeter/billing/charges/usagebased/service/run/service.go
- openmeter/billing/charges/service/flatfee_costbasis_test.go
Summary
This change enables usage-based charges settled through customer credits to use a custom credit currency while the customer invoice remains denominated in fiat.
Usage is realized in the custom currency and available customer credits are allocated in that same currency. Any usage not covered by credits becomes overage and is converted to the invoice currency using the cost basis selected for the charge.
The overage must be invoiced as a credit purchase. The invoice line represents the uncovered custom-currency amount purchased at the charge's persisted cost basis, with the resulting fiat amount becoming the customer's outstanding balance.
Business behavior
This keeps customer credit accounting in the credit currency while ensuring invoices, outstanding balances, and payments remain fiat-denominated.
Validation
make test-nocache— 6,379 tests passed, 15 skippedSummary by CodeRabbit
Greptile Summary
Adds custom-currency usage settlement through fiat invoices.
Confidence Score: 5/5
The PR appears safe to merge within this follow-up review because no new blocking failure eligible for a distinct comment remains.
No blocking failure eligible for this follow-up review remains.
Important Files Changed
Sequence Diagram
sequenceDiagram participant Usage as Usage Rating participant Credits as Credit Allocation participant Charge as Usage Charge participant Invoice as Fiat Invoice participant Ledger as Ledger Usage->>Charge: Realize usage in custom currency Charge->>Credits: Allocate matching customer credits Credits-->>Charge: Return uncovered custom-currency amount Charge->>Charge: Convert overage using persisted cost basis Charge->>Invoice: Create fiat credit-purchase line Invoice->>Ledger: Accrue and settle fiat balanceReviews (3): Last reviewed commit: "fix(customcurrencies): preserve overage ..." | Re-trigger Greptile